﻿using System;
using System.Linq;

using System.Drawing;
using RayDen.Library.Components.Accelleration;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data.Imaging;
using RayDen.Library.Entity.Interface;
using RayDen.Library.Entity.Scene;

namespace RayDen.SmallPT
{
    public class AmbientOcclussionRenderer : BaseRenderer
    {

        public RgbSpectrum BaseColor = new RgbSpectrum(0.15f);
        public RgbSpectrum AddedRadiance = new RgbSpectrum(0.05f);

        public float maxOcclussionRayLength = 10f;

        public AmbientOcclussionRenderer()
        {
            samplesPerPixel = 1;
            shadowRayCount = 16;
        }


        #region IRenderer Members

        public override void RenderPreview(Bitmap output, Action<int> progressCallback)
        {
            //for (int y = 0; y < height; y++) {
            var pixels = new Color[Width * Height];
#if PL
            Parallel.For(0, Height, y =>
#else
            for (int y = 0; y < height; y++)
#endif
            {
                for (int x = 0; x < Width; x++)
                {
                    IRay ray;
                    camera.GetRay(x, y, out ray);
                    var pixelColor = Trace((RayInfo) ray, 1.0f, Air, 0);
                    pixels[x + y * Width] = pixelColor.ToColor();
                }
                //Console.Write("{0}->", height / (y+1) );
            }
#if PL
);
#endif


            Console.WriteLine("{0} Non black pixels", pixels.Count(item => (item.R > 0 || item.G > 0 || item.B > 0)));
            Console.WriteLine("{0} Triangles", scene.Vertices.Length / 3);
            Console.WriteLine("{0} Intersections", Intersections);
            Console.WriteLine("{0} Rays traced", RaysTraced);

            Console.WriteLine("Splatting");
            for (int y = 0; y < Height; y++)
                for (int x = 0; x < Width; x++)
                {
                    output.SetPixel(x, y, pixels[x + (Height - 1 - y) * Width]);
                }

        }

        public override void StartRendering(Action<IFilmFrame, int> taskCallback, Action<IFilmFrame> endTaskCallback)
        {
            var filmFrame = new FilmFrame(Width, Height);

            Console.Write("Rendering :[");
#if PL
            Parallel.For(0, Height, new ParallelOptions() { CancellationToken = this.cancelToken.Token, MaxDegreeOfParallelism = CpuThreadsUsed }, ( y) =>
#else
            for (int y = 0; y < height; y++)
#endif
            {
                for (int x = 0; x < Width; x++)
                {
                    for (int n = 0; n < this.samplesPerPixel; n++)
                    {
                        int ny = y;
                        IRay ray;
                        camera.GetRay(x + (1f - rnd.Value.NextFloat()), (ny) + (1f - rnd.Value.NextFloat()), out ray);
                        var pixelColor = Trace((RayInfo) ray, 1.0f, Air, 0);
                        if (!pixelColor.IsBlack())
                        {
                            //unchecked
                            {
                                filmFrame.Data[unchecked(x + y * Width)] = pixelColor;
                                filmFrame.Weight[unchecked(x + y * Width)] += 1f;
                            }
                        }
                    }
                }
                //for( int x=0; x<width;x++ )
                //    output.SetPixel(pixels[x + y * width].X, pixels[x + y * width].Y, (pixels[x + y * width].Data / pixels[x + y * width].Weight).ToColor());
                taskCallback(filmFrame, y);
            }
#if PL
);
#endif
            if (endTaskCallback != null)
                endTaskCallback(filmFrame);
        }
        #endregion

        #region Bruteforce AO Implementation

        private volatile int RaysTraced, Intersections;
        private RgbSpectrum Trace(RayInfo ray, float weight, MediumInfo currentMedium, int depth)
        {
            IntersectionInfo isect;
            //RaysTraced++;
            stats.totalRays++;
            var color = new RgbSpectrum(0f);
            if (manager.Intersect(ray, out isect))
            {
                stats.intersections++;
                //Intersections++;
                color = this.Shade(currentMedium, weight, ray, isect, depth);
            }
            else
            {
                color = this.ShadeBackground(ray);
            }
            return color;
        }

        private RgbSpectrum ShadeBackground(RayInfo ray)
        {
            if (EnvironmentMap == null)
            {
                return RgbSpectrum.ZeroSpectrum();
            }
            return EnvironmentMap.Le(-ray.Dir);
        }

        private RgbSpectrum Shade(MediumInfo currentMedium, float weight, RayInfo vray, IntersectionInfo isect, int depth)
        {
            var txtn = isect.GeometryInfo.GeoNormal;
            var p = isect.GeometryInfo.HitPoint;
            Vector v1, v2;
            Vector.CoordinateSystem(ref txtn, out v1, out v2);

            var Radiance = BaseColor * Vector.AbsDot(ref txtn, ref vray.Dir);
            for (int i = 0; i < shadowRayCount; i++)
            {
                var dir = MC.CosineSampleHemisphere(rnd.Value.NextFloat(), rnd.Value.NextFloat());
                dir = new Vector(v1.x * dir.x + v2.x * dir.y + txtn.x * dir.z, v1.y * dir.x + v2.y * dir.y + txtn.y * dir.z, v1.z * dir.x + v2.z * dir.y + txtn.z * dir.z);
                stats.intersections++;
                if (!manager.Intersect(new RayInfo(p, dir, 1e-4f, maxOcclussionRayLength)))
                {
                    Radiance += AddedRadiance * (1.0f / shadowRayCount);
                }
            }
            return Radiance;
        }

        #endregion
    }
}